'''
模型验证界面逻辑
'''
import enum
import glob
import json
import shutil
import sys
import os
import time
import traceback
from functools import partial

from datetime import datetime as py_datetime
import cv2
import numpy as np
import torch
from PyQt5 import QtWidgets
from PyQt5.QtCore import pyqtSignal, Qt

from PyQt5.QtWidgets import QApplication, QHeaderView, QWidget, QFileDialog, QTableWidget, QTableWidgetItem, QCheckBox

from model_val import Ui_Form  # 界面控件， QtDesigner生成

# from log import logger

import threading


def tableValue2tableObj(det_record_kv_list, tableWidget):
    '''
    显示数据到表格 （覆盖原来的）
    tableWidget： 表格控件
    det_record_kv_list： 数据 [{},{}]
    :return:
    '''
    try:
        # print('xxxxxxxxxxxxxxx')
        # 获取当前表格的行数
        num_rows = tableWidget.rowCount()
        # 从后往前遍历并删除每一行，以避免因删除行导致索引变动
        for row in reversed(range(num_rows)):
            tableWidget.removeRow(row)

        for row_ind, kv_dict in enumerate(det_record_kv_list):  # 每行
            tableWidget.insertRow(row_ind)  # 添加新行
            for col_ind, (k, v) in enumerate(kv_dict.items()):  # 每列 按dict的key值创建顺序
                if isinstance(v, bool):  # 复选框
                    # 创建一个不可编辑的 QCheckBox
                    checkbox = QCheckBox()
                    checkbox.setCheckState(Qt.Checked if v else Qt.Unchecked)  # 假设复选框处于选中状态
                    checkbox.setEnabled(False)  # 设置 QCheckBox 为不可编辑, 颜色变为灰色
                    tableWidget.setCellWidget(row_ind, col_ind, checkbox)  # 将 QCheckBox 设置为单元格小部件

                    # checkbox_item = QTableWidgetItem()
                    # checkbox_item.setCheckState(Qt.Checked if v else Qt.Unchecked)  # 根据 bool 变量设置初始状态
                    # # checkbox_item.setFlags(checkbox_item.flags() & ~Qt.ItemIsEditable)  # 禁止编辑
                    # tableWidget.setItem(row_ind, col_ind, checkbox_item)

                elif isinstance(v, py_datetime):
                    tableWidget.setItem(row_ind, col_ind, QTableWidgetItem(str(v.strftime('%Y/%m/%d %H:%M:%S'))))
                else:
                    tableWidget.setItem(row_ind, col_ind, QTableWidgetItem(str(v)))

            # break
        # 设置整个表格禁止编辑（可选，如果需要的话）
        tableWidget.setEditTriggers(QTableWidget.NoEditTriggers)
    except:
        print(traceback.format_exc())


class SubDebugWidget(QWidget, Ui_Form):
    '''

    usage:

    '''

    # step2_info_signal = pyqtSignal(list) # 第二步显示信息
    # # step2_run_signal = pyqtSignal(list)
    # step3_info_signal = pyqtSignal(list) # 第三步显示信息

    info_signal = pyqtSignal(list)

    def __init__(self, parent=None, main_ui_handle=None):
        super(SubDebugWidget, self).__init__(parent)

        self.setupUi(self)

        # 额外样式
        self.setStyleSheet("""
                QPushButton {
                    min-width: 80px;
                    padding: 5px;
                    border-radius: 3px;
                    color: white;
                    background-color: dodgerblue;
                }
                QPushButton:hover {
                    background-color: darkblue;
                }

                /* 定义 QGroupBox 样式 */
                QGroupBox {
                    /*font-size: 14px;*/
                    font-weight: bold;
                    border: 1px solid #ccc;
                    border-radius: 8px; /* 使用像素值代替分数 */
                    padding: 1em;
                    margin-top: 1em;
                    background-color: #f5f5f5;
                }

                /* 设置标题样式 */
                QGroupBox::title {
                    subcontrol-origin: margin;
                    /*subcontrol-position: top center;*/
                    padding: 0 1em;
                    color: #333;
                    background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                                    stop: 0 #f5f5f5, stop: 1 #e5e5e5);
                    border-bottom: 1px solid #ccc;
                    border-top-left-radius: 8px;
                    border-top-right-radius: 8px;
                }

                /* 输入框样式 */
                QLineEdit {
                    border: 1px solid #ccc; /* 边框颜色 */
                    border-radius: 4px; /* 边框圆角 */
                    background-color: #fff; /* 背景颜色 */
                }
                /* 输入框焦点样式 */
                QLineEdit:focus {
                    border-color: #80bfff; /* 焦点边框颜色 */
                }
                
                QLabel {
                    background-color: #FFFFFF;
                    color: #00000FF;
                    font-family: Arial;
                    font-size: 14px;
                    border: 2px solid #0000;
                    border-radius: 10px;
                    font-weight: bold;
                    font-style: italic;
                    text-decoration: underline;
                    padding: 20px; /* 上、右、下、左边距均为20px */
                }
                
                #img_globLabel {
                    
                    padding: 0px;
                }
                #save_pathLabel {
                    padding: 0px;
                }
            """)
        self.tableWidget.horizontalHeader().setSectionResizeMode(
            QHeaderView.Interactive | QHeaderView.Stretch)  # 列宽自适应表格控件大小

        self.pushButton.clicked.connect(self.step1_select_labeled_img_slot) # 第一步 选择标注图片
        self.pushButton_4.clicked.connect(self.step1_select_model_slot)  # 第一步 选择模型
        self.pushButton_2.clicked.connect(self.step2_run_slot) # 第二步 执行模型验证
        # self.step2_info_signal.connect(self.step2_info_signal_slot) # 显示第二步相关信息, 结果图片
        # self.step3_info_signal.connect(self.step3_info_signal_slot) # 显示第三步相关信息

        self.info_signal.connect(self.info_signal_slot)  # 显示第三步相关信息
        self.pushButton_3.clicked.connect(self.query_btn_slot)

        self.pushButton_5.clicked.connect(self.count_xg_label_img_btn_slot)
        self.pushButton_6.clicked.connect(self.do_label_img_btn_slot) # 芯歌格式打标
        # self.pushButton_7.clicked.connect(self.do_label_by_model_btn_slot)
        self.pushButton_8.clicked.connect(self.do_labelme_btn_slot)# labelme打标

        # self.step2_run_signal.connect(self.step2_run_signal_slot)

        self.pushButton_11.clicked.connect(self.btn11_slot) # 选择文件夹
        self.pushButton_12.clicked.connect(self.btn12_slot)  # 选择模型
        self.pushButton_13.clicked.connect(self.btn13_slot)  # 模型打标
        self.pushButton_14.clicked.connect(self.btn14_slot)  # 手动微调


        self.pushButton_7.clicked.connect(self.btn7_slot)  # glob复制图片

        self.selected_folder = None # 标注图片
        self.model_path = None # 模型

        self.btn11_img_dir = None # 模型打标 图片目录
        self.btn12_model_path = None # 模型打标 模型地址


        self.main_ui_handle = main_ui_handle
        self.model_val_engine = Model_Val_Engine(ui_handle=self)


        self.textBrowser.append('Linux安全带数据目录：/home/ps/zhangxiancai/data/231215anquandai/train_flball/_add_imgs/')
        self.textBrowser.append('Linux yolov5训练目录：/home/ps/zhangxiancai/yolov5_x/')
        self.textBrowser.append('Linux yolov8训练目录：/home/ps/zhangxiancai/yolov8/')


    def step1_select_labeled_img_slot(self ):
        try:
            file_dialog = QFileDialog()
            # file_path, _ = file_dialog.getOpenFileName(parent=None, caption="选择文件", directory="",
            #                                            filter="所有文件(*.*)")
            # if file_path:
            #     print(f"选择了文件: {file_path}")

            self.selected_folder = file_dialog.getExistingDirectory(parent=None,
                                                                    caption="select the folder with label and image",
                                                                    directory=r"D:\data\231215安全带\test_data\20240326-TRIAL1 4LINES")

            print(f"选择了文件夹: {self.selected_folder}")
            self.label.setText(f'Selected folder: {self.selected_folder}')
            self.textBrowser.append(self.selected_folder)
            self.textBrowser.ensureCursorVisible()
        except:
            print(traceback.format_exc())

    def step1_select_model_slot(self):
        try:
            file_dialog = QFileDialog()
            self.model_path, _ = file_dialog.getOpenFileName(parent=None, caption="select the model", directory=r"D:\code\git\zxc\anquandai\0402\joyson_anquandai\models",
                                                       filter="所有文件(*.pt)")
            if self.model_path:
                print(f"选择了文件: {self.model_path}")
            self.label_4.setText(f'Selected Model: {self.model_path}')
            self.textBrowser.append(f'模型地址：{self.model_path}')
            self.textBrowser.ensureCursorVisible()
        except:
            print(traceback.format_exc())

    def step2_run_slot(self):
        try:
            # self.model_val_engine.test_detect_kakou_v2(is_save=True, glob_str=self.selected_folder)
            # self.step2_run_signal.emit([])
            print('xxx')

            t = threading.Thread(target=self.model_val_engine.test_detect_kakou_v2, args=(True, self.selected_folder, self.model_path))
            self.label_2.setText(f'starting...')
            t.start()
            time.sleep(0.5)

        except:
            print(traceback.format_exc())
    #
    # def step2_info_signal_slot(self, li):
    #     '''
    #     显示步骤二信息
    #     :param li:
    #     :return:
    #     '''
    #     try:
    #         ind, total = li
    #         self.label_2.setText(f'Progress: {ind+1}/{total}')
    #     except:
    #         print(traceback.format_exc())
    #
    # def step3_info_signal_slot(self, li):
    #     '''
    #     显示步骤三信息
    #     :param li:
    #     :return:
    #     '''
    #     try:
    #         save_path, save_excel_path, res_dict = li
    #         self.label_3.setText(f'Visual image folder: {save_path}\nResult table: {save_excel_path}\n{str(res_dict)}')
    #         # 打开结果图片目录
    #         os.startfile(self.selected_folder + '_vision')
    #     except:
    #         print(traceback.format_exc())

    def info_signal_slot(self, li):
        '''
        显示信息
        :param li:
        :return:
        '''
        try:
            type_str, data_list = li[0], li[1:]
            if type_str == 'step2': # 显示运行进度
                ind, total = data_list
                self.label_2.setText(f'Progress: {ind + 1}/{total}')
            elif type_str == 'step3': # 显示结果目录
                save_path, save_excel_path, res_dict = data_list
                self.label_3.setText(f'Visual image folder: {save_path}\nResult table: {save_excel_path}\n{str(res_dict)}')
                # 打开结果图片目录
                os.startfile(self.selected_folder + '_vision')
            elif type_str == 'label_by_model': # 模型打标
                ind, total = data_list
                self.label_5.setText(f'Progress: {ind + 1}/{total}')

        except:
            print(traceback.format_exc())

    def query_btn_slot(self):

        records_list = self.model_val_engine.sqlite_handle.select()

        # records_list 变量加到表格控件对象上
        tableValue2tableObj(records_list, self.tableWidget)


    def count_xg_label_img_btn_slot(self):
        '''
        统计实例个数
        :return:
        '''
        # 输入 标注文件夹
        self.textBrowser_2.clear()

        file_dialog = QFileDialog()
        label_img_dir = file_dialog.getExistingDirectory(parent=None, caption="select the folder with label and image",
                                                        directory=r"D:\data\231215安全带\test_data\20240326-TRIAL1 4LINES")
        print(f"选择了文件夹: {label_img_dir}")
        # self.label_5.setText(f'Selected folder: {label_img_dir}')
        self.textBrowser_2.append(f'Selected folder: {label_img_dir}')
        self.textBrowser.append(label_img_dir)
        self.textBrowser.ensureCursorVisible()

        # 执行
        res_dict = Tools.count_xglabel(label_img_dir)
        # self.label_5.setText(self.label_5.text()+'\n' + json.dumps(res_dict,indent=4))
        self.textBrowser_2.append(json.dumps(res_dict,indent=4))
        #
    def do_label_img_btn_slot(self):

        # cmd_str = r'python D:\code\git\zxc\yolov5_x\test_pyqt_labelsdet.py'
        # print(cmd_str)
        # os.system(cmd_str)
        #
        def work_thread1():
            cmd_str = r'python D:\code\git\zxc\yolov5_x\test_pyqt_labelsdet.py'
            print(cmd_str)
            os.system(cmd_str)
        t = threading.Thread(target=work_thread1)
        t.start()

    def do_labelme_btn_slot(self):
        # cmd_str = r'labelme'
        # print(cmd_str)
        # os.system(cmd_str)

        def work_thread1():
            cmd_str = r'labelme'
            print(cmd_str)
            os.system(cmd_str)
        t = threading.Thread(target=work_thread1)
        t.start()
    # def do_label_by_model_btn_slot(self):
    #     '''模型打标'''
    #     # 1 选择图片文件夹
    #     file_dialog = QFileDialog()
    #     img_dir = file_dialog.getExistingDirectory(parent=None, caption="选择图片文件夹",
    #                                                     directory=r"D:\data\231215安全带\test_data\20240326-TRIAL1 4LINES")
    #     print(f"选择了文件夹: {img_dir}")
    #     self.textBrowser.append(img_dir)
    #     self.textBrowser.ensureCursorVisible()
    #     self.textBrowser_3.append(f'img_dir {img_dir}')
    #
    #     # 2 选择模型
    #     file_dialog = QFileDialog()
    #     model_path, _ = file_dialog.getOpenFileName(parent=None, caption="选择v8分割模型", directory=r"D:\data\231207huoni\trainV8Seg_strap\models\yolov8mSeg_640_strap8",
    #                                                      filter="所有文件(*.pt)")
    #     print(f"选择了文件: {model_path}")
    #     self.textBrowser.append(f'模型地址：{model_path}')
    #     self.textBrowser.ensureCursorVisible()
    #     self.textBrowser_3.append(f'model_path {model_path}')
    #     pass
    #
    #     # 3 运行
    #     threading.Thread(target=Tools.label_by_model,
    #                      args=(img_dir+'\*', model_path)).start()

    def btn11_slot(self):
        # 1 选择图片文件夹
        file_dialog = QFileDialog()
        self.btn11_img_dir = file_dialog.getExistingDirectory(parent=None, caption="选择图片文件夹",
                                                   directory=r"D:\data\231207huoni\采图\strap\20240529\20240529_split_all")
        print(f"选择了文件夹: {self.btn11_img_dir}")
        self.textBrowser.append(f'模型地址：{self.btn11_img_dir}')
        self.textBrowser.ensureCursorVisible()
        self.textBrowser_4.append(f'img_dir {self.btn11_img_dir}')


    def btn12_slot(self):
        # 2 选择模型
        file_dialog = QFileDialog()
        self.btn12_model_path, _ = file_dialog.getOpenFileName(parent=None, caption="选择v8分割模型",
                                                    directory=r"D:\data\231207huoni\trainV8Seg_strap\models\yolov8mSeg_640_strap8",
                                                    filter="所有文件(*.pt)")
        print(f"选择了文件: {self.btn12_model_path}")
        self.textBrowser.append(f'模型地址：{self.btn12_model_path}')
        self.textBrowser.ensureCursorVisible()
        self.textBrowser_4.append(f'model_path {self.btn12_model_path}')

    def btn13_slot(self):
        # 3 运行
        threading.Thread(target=Tools.label_by_model,
                         args=(self.btn11_img_dir+'\*', self.btn12_model_path, self)).start()
        os.startfile(self.btn11_img_dir)

    def btn14_slot(self):
        def work_thread1():
            cmd_str = 'labelme'
            if self.btn11_img_dir is not None:
                cmd_str = rf'labelme {self.btn11_img_dir}'
            print(cmd_str)
            os.system(cmd_str)
        threading.Thread(target=work_thread1).start()
        time.sleep(0.5)

    def btn7_slot(self):
        img_glob = self.img_globLineEdit.text()
        save_path = self.save_pathLineEdit.text()

        if not os.path.exists(save_path):
            os.makedirs(save_path)
        os.startfile(save_path)
        Tools.copy_img(img_glob, save_path)
        threading.Thread(target=Tools.copy_img, args=(img_glob, save_path)).start()
        time.sleep(0.5)


'''
模型验证
'''
from datetime import datetime
from sqlite import ModelVal_Sqlite


sys.path.append(r'D:\code\git\zxc\anquandai\0402\joyson_anquandai')
from test_detect_kakou import get_detect_kakou
class Model_Val_Engine:

    def __init__(self, ui_handle=None):
        self.ui_handle = ui_handle
        self.sqlite_handle = ModelVal_Sqlite()

    def test_detect_kakou_v2(self,
                             is_save=False,
                             glob_str=r'D:\data\231215安全带\train_flball_blue\_add_imgs',
                             model_path= None):
        '''
        测试当前算法在所有训练数据上的正确率
        需要：
        算法，
        图片+芯歌格式标签
        '''
        # pt_dict = torch.load(model_path) # 判断模型类别
        # if attr.hasattr(pt_dict['model'], "yaml_file") and pt_dict['model'].yaml_file[:6]=='yolov5':
        #     alg_type = 'yolov5_det'
        # else:
        #     alg_type = 'yolov8_det'

        if os.path.basename(os.path.dirname(model_path)) == 'weights':
            alg_type = 'yolov8_det'
        else:
            alg_type = 'yolov5_det'
        print(f'alg_type {alg_type}')

        detect_fenglun = get_detect_kakou(alg_type=alg_type, model_path=model_path)  # yolov5_det yolov8_det
        # glob_str = r'D:\data\231215安全带\train_flball_blue\_add_imgs' # 数据, 不在add_imgs目录
        # glob_str = r'D:\data\231215安全带\train_flball\_add_imgs\20240131'

        #
        exts = ['.xml', ]
        ls = []  # xml文件地址list
        for root, dirs, files in os.walk(glob_str):  # os.walk 递归遍历
            for fn in files:
                fn0, ext = os.path.splitext(fn)
                if ext.lower() in exts and '_mini' not in root:  # 非mini
                    fn1 = os.path.join(root, fn)
                    ls.append(fn1)

        res = {}  # clsname:[cnt_tru, cnt_pre, accuracy]
        from common.utils import read_xml_annotation  # 读取xg标签文件
        from common.utils import rect_iou  # iou
        print('compare...')
        for ind, pa_xml in enumerate(ls):  # pre img
            print(f'{ind}/{len(ls)}')
            if self.ui_handle is not None:
                self.ui_handle.info_signal.emit(['step2', ind, len(ls)])
            pa_jpg = pa_xml[:-3] + 'jpg'
            print(f'img: {pa_jpg}')
            try:
                img = cv2.imread(pa_jpg)
            except:
                print(f'not exits {pa_jpg}')
                continue
            t1 = time.time()
            img1, out_pre, end, ends, save_img = detect_fenglun(img)  # [xyxy, cls_name, conf]
            print(out_pre)
            print(f'time {time.time() - t1} s')
            out_true = read_xml_annotation(pa_xml)  # [xyxy, cls_name]

            # 对比
            for ott in out_true:  # per box
                cls_name = ott[1].lower()  # 转为小写字母 20240206
                if cls_name == '???':
                    continue

                isCorrect = False  # 单个标注框是否检测正确, 一个单位
                for otp in out_pre:
                    if cls_name.lower() != otp[1].lower():  # 不区分大小写
                        continue
                    iou = rect_iou(ott[0], otp[0])
                    if iou < 0.1:  # 大于0.1为正确
                        continue
                    isCorrect = True

                # 记录至res
                if not res.get(cls_name):
                    if isCorrect:
                        res[cls_name] = [1, 1]
                    else:
                        res[cls_name] = [1, 0]
                else:
                    if isCorrect:
                        res[cls_name][0] += 1  # 总数
                        res[cls_name][1] += 1  # 检测正确数
                    else:
                        res[cls_name][0] += 1

            if is_save:
                # img_show = img.copy()
                img_show = img1.copy()
                for xyxy, clsname in out_true:
                    x1, y1, x2, y2 = map(int, xyxy)
                    cv2.rectangle(img_show, (x1, y1), (x2, y2), (0, 255, 0), 2)  # 绿色
                    lab = f'{clsname}'
                    cv2.putText(img_show, lab, (x1, y1), fontFace=cv2.FONT_HERSHEY_PLAIN, fontScale=5.0,
                                color=[0, 255, 0],
                                thickness=10)
                for xyxy, clsname, conf in out_pre:
                    x1, y1, x2, y2 = map(int, xyxy)
                    cv2.rectangle(img_show, (x1, y1), (x2, y2), (0, 0, 255), 2)  # 红色
                    lab = f'{clsname}_{np.round(conf, 2)}'
                    cv2.putText(img_show, lab, (x2, y2), fontFace=cv2.FONT_HERSHEY_PLAIN, fontScale=5.0,
                                color=[0, 0, 255],
                                thickness=10)
                # pre2_pa_jpg = pa_jpg
                save_path = glob_str + '_vision' + pa_jpg[len(glob_str):]
                save_dir = os.path.dirname(save_path)
                if not os.path.exists(save_dir):
                    os.makedirs(save_dir)  # 多层创建
                img_show = cv2.resize(img_show, dsize=None, fx=0.25, fy=0.25)
                cv2.imwrite(save_path, img_show)

        import pprint
        for k in res.keys():
            cnt_tru, cnt_pre = res[k]
            acc = cnt_pre / cnt_tru
            acc = np.round(acc, 4)
            res[k].append(acc)
        pprint.pprint(res)

        # 保存至excel
        import pandas as pd
        # 创建一个DataFrame
        # df = pd.DataFrame(res).T.sort_index()
        df = pd.DataFrame(res)
        # 将DataFrame写入Excel文件
        save_excel_path = glob_str + '_vision\\res.xlsx'
        df.to_excel(save_excel_path, index=False)
        if self.ui_handle is not None:
            self.ui_handle.info_signal.emit(['step3', glob_str + '_vision', save_excel_path, res]) # 图片目录，表格路径， 结果精度字典

        # 保存记录至数据库
        record = {
            "LabelImgDir": glob_str, # 标注图片目录
            "ModelPath": model_path, # 模型路径
            "AccuracyJson": json.dumps(res), #  lambda value: json.dumps(value),
            "Date": datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f'),  # 测试时间
        }
        self.sqlite_handle.insert(record)



from ultralytics import YOLO # v8.1
class Tools:

    @staticmethod
    def count_xglabel(glob_str=''):
        '''
        # 1 统计芯歌格式标签的类别和个数
        20240201
        Returns:

        '''
        from common.utils import read_xml_annotation  # 读取xg标签文件
        # glob_str = r'D:\data\231215安全带\train_flball\_add_imgs\20240119\20240119-NG\*\*.xml'

        if glob_str == '':
            glob_str = r'D:\data\231215安全带\train_flball\_add_imgs\20240320 Set25-10 Re-test'

        # ls = glob.glob(glob_str)
        exts = ['.xml', ]
        ls = []  # xml文件地址list
        ls_jpg = []
        for root, dirs, files in os.walk(glob_str):  # os.walk 递归遍历
            for fn in files:
                fn0, ext = os.path.splitext(fn)
                if ext.lower() in exts and '_mini' not in root:  # 非mini
                    fn1 = os.path.join(root, fn)
                    ls.append(fn1)
                if ext.lower() in ['.jpg', '.jpeg'] and '_mini' not in root:  # 非mini
                    fn1 = os.path.join(root, fn)
                    ls_jpg.append(fn1)
        print(f'ls_xml {len(ls)}')
        print(f'ls_jpg {len(ls_jpg)}')

        res = {}  # clsname: cnt
        for i in ls:
            xyxy_clsnames = read_xml_annotation(i)
            for xyxy, clsname in xyxy_clsnames:
                if not res.get(clsname):
                    res[clsname] = 1
                else:
                    res[clsname] += 1

        import pprint
        print(f'Folder: \n{glob_str}')
        print('Number of each defect type: ')
        pprint.pprint(res)

        cnt = 0
        for k, v in res.items():
            cnt += v
        print(cnt)
        return res

    @staticmethod
    def label_by_model(
            img_glob = r'D:\data\231207huoni\采图\strap\20240528\other\split_all\*.jpg',
            model_path = r"D:\data\231207huoni\trainV8Seg_strap\models\yolov8mSeg_640_strap5\weights\best.pt",
            ui_handle = None):
        '''
        v8分割模型；labelme格式
        Args:
            img_glob:
            model_path:

        Returns:

        '''
        try:
            # img_glob = r'D:\data\231207huoni\采图\strap\20240528\other\split_all\*.jpg'
            # model_path = r"D:\data\231207huoni\trainV8Seg_strap\models\yolov8mSeg_640_strap5\weights\best.pt"

            ls = glob.glob(img_glob) # 图片
            # v8seg_cable = get_yolov8_seg_predict(model_path) # 算法
            model = YOLO(model_path)

            for ind, img_path in enumerate(ls): # per image
                print(f'{ind}/{len(ls)} {img_path}')
                if ui_handle is not None:
                    ui_handle.info_signal.emit(['label_by_model', ind, len(ls)])
                img = cv2.imread(img_path)
                # img_show, out = v8seg_cable(img, isShowMask=False)
                # # out: [[xyxy],cls_ind, clsname, conf,mask] [list(1*4),int,str,float,numpy(1*h*w)]

                results = model.predict(img, imgsz=640, conf=0.5, iou=0.5,
                                        device='0', retina_masks=True)  # retina_masks 高分辨率mask
                result = results[0]  # one img
                boxes = result.boxes  # Boxes object for bounding box outputs
                masks = result.masks  # Masks object for segmentation masks outputs  n*448*640
                boxes = boxes.cpu().numpy()
                if boxes.shape[0] == 0:
                    continue
                # masks = masks.cpu().numpy()  # 当无目标时masks = None

                # yolov8输出 转为labelme分割 xml
                labelme_dict = {
                  "version": "5.3.1",
                  "flags": {},
                  "shapes": [],
                  "imagePath": os.path.basename(img_path),
                  "imageData": None,
                  "imageHeight": img.shape[0],
                  "imageWidth": img.shape[1]
                }
                for i in range(boxes.shape[0]): # per instance
                    # xyxy = boxes.xyxy[i, :].tolist()
                    # conf = boxes.conf[i]
                    cls_ind = int(boxes.cls[i])
                    cls_name = result.names[cls_ind]
                    # mask = masks[i].data  # numpy 1*h*w  0.0 or 1.0
                    xys = masks[i].xy[0]  # 像素坐标列表 numpy n*2
                    xys = xys.tolist()
                    print(f'len(xys) {len(xys)}')

                    # if len(xys) > 40:
                    #     down_sample = max(int(len(xys) / 20), 1)
                    # else:
                    #     down_sample = max(int(len(xys) / 10), 1)

                    down_sample = 40
                    if len(xys)/down_sample < 5: # 不足5个点
                        down_sample = max(int(len(xys) / 5), 1) # 下采样至5个点
                    xys = xys[::down_sample]

                    shapes_item_dict = {
                        "label": cls_name,
                        "points": xys, # [[x,y],[]...]
                        "group_id": None,
                        "description": "",
                        "shape_type": "polygon",
                        "flags": {},
                        "mask": None
                    }

                    labelme_dict['shapes'].append(shapes_item_dict)

                # save
                xml_save_path = img_path[:-3] + 'json'
                with open(xml_save_path, 'w') as f:
                    f.write(json.dumps(labelme_dict, indent=4)) # indent=4 自动缩进4
        except:
            print(traceback.format_exc())

    @staticmethod
    def copy_img(img_glob = '', save_path = ''):
        # img_glob = ''
        # save_path = ''
        try:
            ls = glob.glob(img_glob)
            # if not os.path.exists(save_path):
            #     os.makedirs(save_path)
            for ind, i in enumerate(ls):
                # name = os.path.basename(i)
                print(f'{ind}/{len(ls)}')
                shutil.copy(i, save_path)
        except:
            print(traceback.format_exc())


    @staticmethod
    def train_workflow(self):
        from ssh.ssh_client import SSHClient
        ssh_handle = SSHClient('','','')
        # 数据上传（）
        # 获取更新数据列表
        local_train_data_dir = r'D:\data\231207huoni\trainV8Seg_screw'
        remote_train_data_dir = '/home/ps/zhangxiancai/data/231207huoni/trainV8Seg_screw'

        local_add_imgs_dir = fr'{local_train_data_dir}/add_imgs'
        local_img_dirs = glob.glob(f'{local_add_imgs_dir}/*[!mini][!txt]') # 目前只考虑第一级子文件夹


        output, error, exit_status = ssh_handle.execute_command(f'ls {remote_train_data_dir}/add_imgs ')
        if exit_status==1:
            print('execute_command error')
            return

        ssh_handle.upload_file()


if __name__ == '__main__':
    try:
        app = QtWidgets.QApplication(sys.argv)
        mainWnd = SubDebugWidget()
        # ui = Ui_MainWindow()
        # ui.setupUi(mainWnd)
        mainWnd.show()
        sys.exit(app.exec())
    except:
        print(traceback.format_exc())
        # logger.error(traceback.format_exc())